import csv
from time import time
from pprint import pprint
from collections import defaultdict
from traceback import *
import multiprocessing
from functools import partial

from GameRunner import GameRunner, NO_LIMIT
from BlokusGameConstants import Players, GameOption, BoardSize, ORDERED_SHAPES_MODE, TIE
from BlokusGameInput import IllegalShapeFileFormatException
from BlokusGameState import BlokusGameState
from BlokusGameAgentExampleX import BlokusGameAgentExample
from BlokusGameAgentFTW import BlokusGameAgentFTW

import RunExpriment1 # for the getattr()..

'''
	Runing this script should output all of the raw information for experiment 1 as defined
	in the assignment specification doc.
	players compared should be defined each in a faile named 'BlokusAgent<choose the name>.py'.
	Output should be to file 'output1.txt' (or csv). 
	Alternatively, you may split the output into multiple files named 
	'output1_<a relevant name>.txt' (or csv).
'''
OUTPUT_FILENAME = 'output1.csv'
OUTPUT_TABLE_FILENAME = 'output1_table.csv'
PLAYER_TYPES = [BlokusGameAgentExample.__name__, BlokusGameAgentFTW.__name__]
PLAYER_DEPTHS = ['2','4','6']#, ['2','4','6']
MATCHES_PER_SET = 10 #10

CSV_PARAMS = {'delimiter':',', 'quotechar': '|', 'quoting' : csv.QUOTE_MINIMAL}

def getMatchesFought(filename = OUTPUT_FILENAME):
    matches = defaultdict(list)
    try:
        with open(filename, 'r') as csvfile:
            csvreader = csv.reader(csvfile, **CSV_PARAMS)
            for row in csvreader:
                p1type, p1depth, p2type, p2depth, winner, runtime = row
                matches[(p1type, p1depth, p2type, p2depth)].append((winner, runtime))
    except IOError:
        pass # matches will be empty

    return matches

def getMatchesToFight(alreadyFoughtMatches = defaultdict(list)):
    matchesToFight = {}
    for p1depth in PLAYER_DEPTHS:
        for p2depth in PLAYER_DEPTHS:
            for p1type in PLAYER_TYPES:
                for p2type in PLAYER_TYPES:
                    if (p1type, p1depth) != (p2type, p2depth):
                        matchesToFight[(p1type, p1depth, p2type, p2depth)] = MATCHES_PER_SET - len(alreadyFoughtMatches[(p1type, p1depth, p2type, p2depth)])
    return matchesToFight


def _fightStar(params):
    fight(*params)


def fight(p1type_str, p1depth, p2type_str, p2depth, out_filename = OUTPUT_FILENAME):
    try:
        winner, runtime = runFight(p1type_str, p1depth, p2type_str, p2depth)
        with open(out_filename, 'a') as csvfile:
            #csvwriter = csv.writer(csvfile, **CSV_PARAMS)
            #csvwriter.writerow([p1type_str, p1depth, p2type_str, p2depth, winner, runtime])
            csvfile.write(CSV_PARAMS['delimiter'].join([p1type_str, str(p1depth), p2type_str, str(p2depth), winner, str(runtime)]) + '\n')
    except Exception, e:
        print "Fight exception: ", p1type_str, p1depth, p2type_str, p2depth
        print_exc(e)
        return


def runFight(p1type_str, p1depth_str, p2type_str, p2depth_str):
    start = time()

    (boardSize, orderedShapesMode, timeLimit) = (BoardSize.TWENTY, ORDERED_SHAPES_MODE, NO_LIMIT)
    
    agents = {}

    agents[Players.FIRST] = getattr(RunExpriment1, p1type_str)()
    agents[Players.FIRST].fixedDepth = int(p1depth_str)
    agents[Players.SECOND] = getattr(RunExpriment1, p2type_str)()
    agents[Players.SECOND].fixedDepth = int(p2depth_str)
    
    state = BlokusGameState().setup(boardSize, 'shapes.txt', orderedShapesMode)

    print "Running fight: ", p1type_str, p1depth_str, p2type_str, p2depth_str
        
    (winner, finalState) = (None, None)
    
    finalState = GameRunner(state, agents, timeLimit).run(isPrintStats = False)
    winner = finalState.getWinner()

    runtime = time() - start
    print "Fight ended: ", p1type_str, p1depth_str, p2type_str, p2depth_str
    print "\tWinner: ", winner, ", run time: ", runtime

    return (winner, runtime)


def fightRemaining(matchesToFight, out_filename = OUTPUT_FILENAME):
    matches = []
    for matchset, count in matchesToFight.iteritems():
        matches.extend([matchset + (out_filename,)] * count)

    pool = multiprocessing.Pool(multiprocessing.cpu_count() - 1)
    matches.sort(key = lambda m : int(m[1])**10 + int(m[3])**10) # sort 2-2,2-4,4-2,4-4,2-6...
    pool.map(_fightStar, matches)


def summerizeMatches(matches):
    summerizedMatches = defaultdict(lambda : {'won' : 0, 'lost': 0, 'tied' : 0, 'points' : 0})
    for mset_tuple, fights in matches.iteritems():
        mset = summerizedMatches[mset_tuple]
        for winner, runtime in fights:
            if winner == Players.FIRST:
                mset['won'] += 1
                mset['points'] += 1
            elif winner == TIE:
                mset['tied'] += 1
                mset['points'] += 0.5
            elif winner == Players.SECOND:
                mset['lost'] += 1
                mset['points'] += 0
            else:
                raise ValueError()

    return summerizedMatches

def createResultsTable(summerizedMatches, out_filename = OUTPUT_TABLE_FILENAME):
    outf = open(out_filename,'w')
    # print header
    for p1type in PLAYER_TYPES:
        for p1depth in PLAYER_DEPTHS:
            outf.write(',{}:{}'.format(p1type.replace('BlokusGameAgent',''), p1depth))

    for p2type in PLAYER_TYPES:
        for p2depth in PLAYER_DEPTHS:
            outf.write('\n{}:{}'.format(p2type.replace('BlokusGameAgent',''), p2depth))
            for p1type in PLAYER_TYPES:
                for p1depth in PLAYER_DEPTHS:
                    if (p1type, p1depth) != (p2type, p2depth):
                        outf.write(',({won}|{tied}|{lost}) -> {points} pnts'.format(**summerizedMatches[(p1type, p1depth, p2type, p2depth)]))
                    else:
                        outf.write(',---')



if __name__ == '__main__':
    matchesFought = getMatchesFought()
    #print "Matches already fought:"
    #pprint(dict(matchesFought))
    
    matchesToFight = getMatchesToFight(matchesFought)
    print "\nMatches to fight:"
    pprint(matchesToFight)
    fightRemaining(matchesToFight)

    matchesFought = getMatchesFought()
    summerizedMatches = summerizeMatches(matchesFought)

    print "\nSummerized results:"
    pprint(dict(summerizedMatches))
    createResultsTable(summerizedMatches)

    